from time import timezone

from django.db import models
from django.contrib.auth.models import User
from django.core.validators import (
    MinValueValidator, MaxValueValidator, RegexValidator
)
from django.core.exceptions import ValidationError
import uuid
import datetime


class Student(models.Model):
    """考研学生模型：与User一对一关联，确保user_id非空"""
    # 核心关联字段：与Django内置User模型一对一，强制非空
    user = models.OneToOneField(
        User,
        on_delete=models.CASCADE,
        related_name='student',
        verbose_name="关联登录账号",
        null=False,  # 数据库层面禁止为空
        blank=False  # 表单层面禁止为空
    )

    # 主键：使用UUID避免ID泄露
    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False,
        verbose_name="学生ID"
    )

    # 基础信息字段
    name = models.CharField(
        max_length=64,
        verbose_name="学生姓名",
        null=False,
        blank=False,
        help_text="学生真实姓名，必填"
    )
    phone = models.CharField(
        max_length=11,
        verbose_name="手机号",
        blank=True,
        validators=[RegexValidator(
            regex=r'^1[3-9]\d{9}$',
            message="请输入11位有效手机号（如：13812345678）"
        )],
        help_text="可选，用于联系学生"
    )
    email = models.EmailField(
        verbose_name="邮箱",
        blank=True,
        help_text="可选，用于发送通知"
    )

    # 考研相关字段（均有默认值，避免空值）
    # 考研战数
    ATTEMPT_CHOICES = [
        ('一战', '一战'),
        ('二战', '二战'),
        ('三战', '三战'),
        ('四战及以上', '四战及以上')
    ]
    attempt_count = models.CharField(
        max_length=16,
        choices=ATTEMPT_CHOICES,
        default='一战',
        verbose_name="考研战数"
    )

    # 学历背景
    EDUCATION_CHOICES = [
        ('双非二本', '双非二本'),
        ('双非一本', '双非一本'),
        ('211', '211'),
        ('985', '985')
    ]
    education_background = models.CharField(
        max_length=16,
        choices=EDUCATION_CHOICES,
        default='双非一本',
        verbose_name="学历背景"
    )

    # 考研状态
    STATUS_CHOICES = [
        ('备考中', '备考中'),
        ('已上岸', '已上岸'),
        ('未上岸-二战', '未上岸-二战'),
        ('未上岸-放弃', '未上岸-放弃')
    ]
    postgraduate_status = models.CharField(
        max_length=16,
        choices=STATUS_CHOICES,
        default='备考中',
        verbose_name="考研状态"
    )

    # 院校专业信息
    undergraduate_school = models.CharField(
        max_length=128,
        verbose_name="本科院校",
        blank=True
    )
    undergraduate_major = models.CharField(
        max_length=128,
        verbose_name="本科专业",
        blank=True
    )
    target_school = models.CharField(
        max_length=128,
        verbose_name="目标院校",
        blank=True
    )
    target_major = models.CharField(
        max_length=128,
        verbose_name="目标专业",
        blank=True
    )
    target_code = models.CharField(
        max_length=32,
        verbose_name="专业代码",
        blank=True
    )
    exam_type = models.CharField(
        max_length=32,
        choices=[('学硕', '学硕'), ('专硕', '专硕')],
        blank=True,  # 表单层面可以为空
        null=True,  # 数据库层面可以为空
        verbose_name="考研类型",
        help_text="可选，学硕或专硕"
    )

    # 考试地区信息（新增）
    EXAM_REGION_CHOICES = [
        ('北京', '北京地区（政治压分严重）'),
        ('上海', '上海地区（判卷严格）'),
        ('江苏', '江苏地区（判卷严格）'),
        ('浙江', '浙江地区（判卷较严）'),
        ('湖北', '湖北地区（中等严格）'),
        ('广东', '广东地区（相对宽松）'),
        ('四川', '四川地区（相对宽松）'),
        ('其他', '其他地区（标准判卷）'),
    ]
    target_exam_region = models.CharField(
        max_length=10,
        choices=EXAM_REGION_CHOICES,
        default='其他',
        verbose_name="目标考试地区",
        help_text="选择报考院校所在地区，影响政治等主观题评分"
    )

    # 考试科目信息
    has_politics = models.BooleanField(
        default=True,
        verbose_name="考政治"
    )
    has_english = models.BooleanField(
        default=True,
        verbose_name="考英语"
    )
    english_type = models.CharField(
        max_length=16,
        choices=[('不考', '不考'), ('英语一', '英语一'), ('英语二', '英语二')],
        default='不考',
        verbose_name="英语类型"
    )
    has_math = models.BooleanField(
        default=True,
        verbose_name="考数学"
    )
    math_type = models.CharField(
        max_length=16,
        choices=[('不考', '不考'), ('数学一', '数学一'), ('数学二', '数学二'), ('数学三', '数学三')],
        default='不考',
        verbose_name="数学类型"
    )

    # 专业课一（原有）
    professional_subject_name = models.CharField(
        max_length=128,
        verbose_name="专业课一名称",
        blank=True
    )
    professional_subject_code = models.CharField(
        max_length=16,
        verbose_name="专业课一代码",
        blank=True
    )

    # 专业课二（新增）
    has_professional_2 = models.BooleanField(
        default=False,
        verbose_name="考专业课二",
        help_text="是否考第二门专业课"
    )
    professional_subject_2_name = models.CharField(
        max_length=128,
        blank=True,
        verbose_name="专业课二名称",
        help_text="第二门专业课的名称"
    )
    professional_subject_2_code = models.CharField(
        max_length=16,
        blank=True,
        verbose_name="专业课二代码",
        help_text="第二门专业课的代码"
    )

    # 其他信息
    notes = models.TextField(
        blank=True,
        verbose_name="备注",
        help_text="可填写学习情况、特殊需求等"
    )
    created_at = models.DateTimeField(
        auto_now_add=True,
        verbose_name="创建时间"
    )
    last_updated = models.DateTimeField(
        auto_now=True,
        verbose_name="最后更新时间"
    )

    class Meta:
        verbose_name = "考研学生"
        verbose_name_plural = "考研学生管理"
        ordering = ['-created_at']  # 按创建时间倒序，最新添加的在前面
        indexes = [
            models.Index(fields=['name']),  # 姓名索引，加速搜索
            models.Index(fields=['target_school']),  # 目标院校索引
            models.Index(fields=['postgraduate_status']),  # 考研状态索引
            models.Index(fields=['target_exam_region']),  # 新增：考试地区索引
        ]

    def __str__(self):
        """后台显示学生名称+目标院校"""
        return f"{self.name} - {self.target_school or '未设置目标院校'}"

    @classmethod
    def create_with_user(cls, username, password, **student_data):
        """
        类方法：创建学生时自动生成关联的登录账号
        """
        print(f"DEBUG: 开始创建用户和学生，username={username}")
        print(f"DEBUG: student_data keys: {list(student_data.keys())}")

        # 1. 校验用户名是否已存在
        if User.objects.filter(username=username).exists():
            raise ValidationError(f"用户名「{username}」已被占用，请更换")

        # 2. 创建普通用户
        user = None
        try:
            user = User.objects.create_user(
                username=username,
                password=password,
                is_superuser=False,
                is_staff=False,
                email=student_data.get('email', '')
            )
            print(f"DEBUG: User创建成功，user_id={user.id}, username={user.username}")
        except Exception as e:
            print(f"DEBUG: 创建User失败: {str(e)}")
            raise ValidationError(f"创建用户账号失败：{str(e)}")

        # 3. 强制删除student_data中的user字段
        student_data.pop('user', None)
        print(f"DEBUG: 清理后 student_data keys: {list(student_data.keys())}")

        # 4. 显式创建Student并绑定User
        try:
            print(f"DEBUG: 准备创建Student，user_id={user.id}")

            # 方法1：使用 objects.create
            student = cls.objects.create(
                user=user,  # 关键：确保user关联
                **student_data
            )
            print(f"DEBUG: Student创建成功，student_id={student.id}")

            # 立即检查关联状态
            print(f"DEBUG: student.user_id = {student.user_id}")
            print(f"DEBUG: hasattr student.user = {hasattr(student, 'user')}")

            # 重新从数据库加载
            student.refresh_from_db()
            print(f"DEBUG: 刷新后 student.user_id = {student.user_id}")

            return student

        except Exception as e:
            print(f"DEBUG: 创建Student失败: {str(e)}")
            # 如果创建Student失败，删除已创建的User
            if user:
                user.delete()
                print(f"DEBUG: 已回滚删除User: {user.id}")
            raise ValidationError(f"创建学生记录失败：{str(e)}")

    def save(self, *args, **kwargs):
        """保存前的验证：确保user字段不为空"""
        if not self.user_id:
            raise ValueError("Student必须关联一个User账号")
        super().save(*args, **kwargs)

    @property
    def exam_subjects_count(self):
        """计算考试科目总数"""
        count = 0
        if self.has_politics:
            count += 1
        if self.has_english:
            count += 1
        if self.has_math:
            count += 1
        if self.professional_subject_name:
            count += 1
        if self.has_professional_2 and self.professional_subject_2_name:
            count += 1
        return count


class PastExamScore(models.Model):
    """
    往年考试成绩记录（仅关联二战及以上学生）
    - 数据约束：避免无效成绩（如超分、年份错误）
    - 业务扩展：记录上岸状态，用于统计上岸率
    """
    student = models.ForeignKey(
        Student,
        on_delete=models.CASCADE,
        related_name="past_scores",
        # 仅允许二战及以上学生添加往年成绩
        limit_choices_to={'attempt_count__in': ['二战', '三战', '四战及以上']},
        verbose_name="关联学生"
    )

    # 1. 基础成绩信息
    exam_year = models.IntegerField(
        verbose_name="考试年份",
        validators=[
            MinValueValidator(2000, message="年份不能早于2000年"),
            MaxValueValidator(datetime.datetime.now().year, message="年份不能晚于今年")
        ]
    )
    total_score = models.FloatField(
        verbose_name="总分",
        validators=[
            MinValueValidator(0, message="总分不能为负数"),
            MaxValueValidator(500, message="考研总分最高为500分")
        ]
    )

    # 2. 单科成绩（按科目满分约束）
    politics_score = models.FloatField(
        null=True,
        blank=True,
        verbose_name="政治成绩",
        validators=[MinValueValidator(0), MaxValueValidator(100)],  # 政治满分100
        help_text="满分100分，未考填“无”"
    )
    english_score = models.FloatField(
        null=True,
        blank=True,
        verbose_name="英语成绩",
        validators=[MinValueValidator(0), MaxValueValidator(100)],  # 英语满分100
        help_text="满分100分，未考填“无”"
    )
    math_score = models.FloatField(
        null=True,
        blank=True,
        verbose_name="数学成绩",
        validators=[MinValueValidator(0), MaxValueValidator(150)],  # 数学满分150
        help_text="满分150分，未考填“无”"
    )
    professional_score = models.FloatField(
        null=True,
        blank=True,
        verbose_name="专业课成绩",
        validators=[MinValueValidator(0), MaxValueValidator(300)],  # 专业课满分300（两门）
        help_text="满分300分（两门专业课），未考填“无”"
    )

    # 3. 业务扩展字段
    is_admitted = models.BooleanField(
        default=False,
        verbose_name="是否上岸",
        help_text="该年份考试是否被目标院校录取"
    )
    notes = models.TextField(
        blank=True,
        verbose_name="备注",
        help_text="记录特殊情况（如：缺考、复试失利、调剂情况）"
    )

    class Meta:
        verbose_name = "往年成绩"
        verbose_name_plural = "往年成绩记录"
        ordering = ['-exam_year']
        # 性能优化：复合索引（避免重复添加同一学生同一年成绩）
        indexes = [
            models.Index(fields=['student', 'exam_year']),  # 学生+年份唯一查询
            models.Index(fields=['is_admitted']),  # 按上岸状态统计
        ]
        # 数据唯一性约束：同一学生不能添加同一年的成绩
        unique_together = ['student', 'exam_year']

    def __str__(self):
        return f"{self.student.name} - {self.exam_year}年考研成绩（总分：{self.total_score}）"

    def clean(self):
        """数据清洁：校验单科成绩总和与总分一致（避免数据矛盾）"""
        # 1. 计算已填写的单科成绩总和
        subject_scores = []
        if self.politics_score is not None:
            subject_scores.append(self.politics_score)
        if self.english_score is not None:
            subject_scores.append(self.english_score)
        if self.math_score is not None:
            subject_scores.append(self.math_score)
        if self.professional_score is not None:
            subject_scores.append(self.professional_score)
        total_subject = sum(subject_scores)

        # 2. 允许 0.5 分误差（浮点精度问题）
        if abs(total_subject - self.total_score) > 0.5:
            raise ValidationError(
                f"单科成绩总和（{total_subject}分）与总分（{self.total_score}分）不一致，请检查！"
            )

    def save(self, *args, **kwargs):
        """保存前自动执行数据清洁（确保数据有效性）"""
        self.clean()
        super().save(*args, **kwargs)


class StudyGoal(models.Model):
    """
    学习目标模型（关联学生各科目目标）
    - 动态科目：匹配学生实际考试科目
    - 分数约束：按科目类型限制目标分数
    """
    student = models.ForeignKey(
        Student,
        on_delete=models.CASCADE,
        related_name="goals",
        verbose_name="关联学生"
    )

    # 1. 目标基础信息（科目类型匹配学生考试科目）
    SUBJECT_TYPE_CHOICES = [
        ('政治', '政治'),
        ('英语一', '英语一'),
        ('英语二', '英语二'),
        ('数学一', '数学一'),
        ('数学二', '数学二'),
        ('数学三', '数学三'),
        ('专业课', '专业课'),
    ]
    subject_type = models.CharField(
        max_length=20,
        choices=SUBJECT_TYPE_CHOICES,
        verbose_name="科目类型"
    )

    target_score = models.FloatField(verbose_name="目标分数")
    current_level = models.CharField(
        max_length=20,
        choices=[('基础薄弱', '基础薄弱'), ('一般', '一般'), ('较好', '较好'), ('优秀', '优秀')],
        default='一般',
        verbose_name="当前水平"
    )

    # 2. 目标优先级与时间
    priority = models.IntegerField(
        default=1,
        verbose_name="优先级",
        validators=[MinValueValidator(1), MaxValueValidator(5)],
        help_text="1-5，数字越大优先级越高"
    )
    deadline = models.DateField(
        verbose_name="目标截止日期",
        null=True,
        blank=True,
        validators=[
            MinValueValidator(
                datetime.datetime.now().date(),
                message="截止日期不能早于今天"
            )
        ]
    )

    # 3. 目标状态
    is_achieved = models.BooleanField(default=False, verbose_name="是否达成")
    created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")

    class Meta:
        verbose_name = "学习目标"
        verbose_name_plural = "学习目标"
        ordering = ['-priority', 'subject_type']  # 按优先级降序、科目类型排序

    def __str__(self):
        status = "已达成" if self.is_achieved else "未达成"
        return f"{self.student.name} - {self.subject_type}：{self.target_score}分（{status}）"

    def clean(self):
        """数据清洁：按科目类型限制目标分数（避免超满分目标）"""
        # 政治/英语：目标分 ≤100
        if self.subject_type in ['政治', '英语一', '英语二']:
            if self.target_score < 0 or self.target_score > 100:
                raise ValidationError(f"{self.subject_type}目标分需在 0-100 分之间！")
        # 数学：目标分 ≤150
        elif self.subject_type in ['数学一', '数学二', '数学三']:
            if self.target_score < 0 or self.target_score > 150:
                raise ValidationError(f"{self.subject_type}目标分需在 0-150 分之间！")
        # 专业课：目标分 ≤300
        elif self.subject_type == '专业课':
            if self.target_score < 0 or self.target_score > 300:
                raise ValidationError("专业课目标分需在 0-300 分之间！")

    def save(self, *args, **kwargs):
        """保存前执行数据清洁（确保目标分数有效）"""
        self.clean()
        super().save(*args, **kwargs)


class TargetScore(models.Model):
    """
    目标成绩模型 - 完全匹配新的学生模型结构
    支持：政治、英语、数学、专业课一、专业课二的完整目标设定
    """
    student = models.ForeignKey(
        Student,
        on_delete=models.CASCADE,
        related_name="target_scores",
        verbose_name="关联学生"
    )

    # 1. 科目类别 - 完全匹配新的学生模型
    SUBJECT_CATEGORY_CHOICES = [
        ('politics', '政治'),
        ('english', '英语'),
        ('math', '数学'),
        ('professional_1', '专业课一'),
        ('professional_2', '专业课二'),
    ]
    subject_category = models.CharField(
        max_length=20,
        choices=SUBJECT_CATEGORY_CHOICES,
        verbose_name="科目类别"
    )

    # 动态科目显示名称
    display_name = models.CharField(
        max_length=50,
        verbose_name="显示名称",
        help_text="自动生成的科目显示名称"
    )

    # 2. 分数目标体系（三档目标）
    ideal_target = models.FloatField(
        verbose_name="理想目标分数",
        help_text="努力争取达到的最高分数"
    )
    realistic_target = models.FloatField(
        verbose_name="实际目标分数",
        help_text="比较有把握达到的分数"
    )
    safety_target = models.FloatField(
        verbose_name="保底分数",
        help_text="必须确保的最低分数"
    )
    current_level = models.FloatField(
        default=0,
        verbose_name="当前水平分数",
        help_text="最近测试或评估的分数"
    )

    # 3. 难度与信心评估
    BASE_DIFFICULTY_CHOICES = [
        (1, '零基础 - 需要从最基础开始学习'),
        (2, '基础薄弱 - 有少量基础但不系统'),
        (3, '基础一般 - 掌握基本概念但应用不熟'),
        (4, '基础较好 - 能解决大部分基础题'),
        (5, '基础扎实 - 主要需要提高和冲刺'),
    ]
    base_difficulty = models.IntegerField(
        choices=BASE_DIFFICULTY_CHOICES,
        default=3,
        verbose_name="基础水平"
    )

    GOAL_DIFFICULTY_CHOICES = [
        (1, '很容易 - 目标比当前水平略高'),
        (2, '较容易 - 需要一定努力但可行'),
        (3, '有挑战 - 需要较多努力和时间'),
        (4, '很困难 - 需要大量投入和良好方法'),
        (5, '极困难 - 需要超常努力和运气'),
    ]
    goal_difficulty = models.IntegerField(
        choices=GOAL_DIFFICULTY_CHOICES,
        default=3,
        verbose_name="目标难度"
    )

    confidence_level = models.IntegerField(
        default=50,
        validators=[MinValueValidator(0), MaxValueValidator(100)],
        verbose_name="达成信心(%)",
        help_text="0-100，对达成实际目标的信心程度"
    )

    # 4. 自动使用学生的考试地区
    # 注意：这里不存储地区，而是使用学生的target_exam_region
    # 自动计算的地域调整分数
    region_adjustment = models.FloatField(
        default=0,
        verbose_name="地域调整分数",
        help_text="自动根据科目和地区计算的分数调整"
    )

    # 5. 备考阶段管理
    STUDY_PHASE_CHOICES = [
        ('not_started', '🆕 未开始 - 尚未系统复习'),
        ('basic_review', '📚 基础复习 - 学习基础知识'),
        ('topic_training', '🎯 专题训练 - 分专题强化'),
        ('mock_practice', '📊 模拟练习 - 真题模拟训练'),
        ('final_review', '🔍 最后冲刺 - 查漏补缺'),
        ('maintaining', '⚡ 保持状态 - 考前调整'),
    ]
    study_phase = models.CharField(
        max_length=20,
        choices=STUDY_PHASE_CHOICES,
        default='not_started',
        verbose_name="学习阶段"
    )

    # 6. 重点与策略
    key_weak_points = models.TextField(
        blank=True,
        verbose_name="重点薄弱环节",
        help_text="该科目中需要重点突破的知识点或题型"
    )

    key_strengths = models.TextField(
        blank=True,
        verbose_name="优势项目",
        help_text="已掌握较好的部分，可以保持的优势"
    )

    study_strategy = models.TextField(
        blank=True,
        verbose_name="学习策略",
        help_text="针对性的学习方法和计划安排"
    )

    # 7. 学习资源与时间
    primary_materials = models.TextField(
        blank=True,
        verbose_name="主要学习资料",
        help_text="使用的教材、网课、习题集等"
    )

    weekly_hours = models.IntegerField(
        default=0,
        verbose_name="每周学习时长(小时)",
        help_text="计划每周投入该科目的学习时间"
    )

    # 8. 进度跟踪
    overall_progress = models.IntegerField(
        default=0,
        validators=[MinValueValidator(0), MaxValueValidator(100)],
        verbose_name="整体进度(%)"
    )

    last_mock_score = models.FloatField(
        null=True,
        blank=True,
        verbose_name="最近模拟成绩"
    )

    mock_score_trend = models.CharField(
        max_length=10,
        choices=[('上升', '上升'), ('稳定', '稳定'), ('下降', '下降'), ('波动', '波动')],
        blank=True,
        verbose_name="成绩趋势"
    )

    # 9. 时间规划
    study_start_date = models.DateField(
        null=True,
        blank=True,
        verbose_name="开始复习日期"
    )

    target_completion_date = models.DateField(
        null=True,
        blank=True,
        verbose_name="目标完成日期",
        help_text="计划完成主要复习内容的时间"
    )

    # 10. 状态标记
    is_active = models.BooleanField(
        default=True,
        verbose_name="是否正在备考"
    )

    priority = models.IntegerField(
        default=3,
        validators=[MinValueValidator(1), MaxValueValidator(5)],
        verbose_name="备考优先级",
        help_text="1-5，数字越大优先级越高"
    )

    # 11. 系统字段
    created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
    updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
    notes = models.TextField(blank=True, verbose_name="备注信息")

    class Meta:
        verbose_name = "目标成绩"
        verbose_name_plural = "目标成绩管理"
        ordering = ['student', 'subject_category']
        unique_together = ['student', 'subject_category']
        indexes = [
            models.Index(fields=['student', 'subject_category']),
            models.Index(fields=['is_active', 'priority']),
        ]

    def __str__(self):
        return f"{self.student.name} - {self.display_name}：{self.current_level}→{self.realistic_target}"

    def save(self, *args, **kwargs):
        """保存前自动处理"""
        # 检查student是否设置
        if not self.student_id:
            raise ValueError("TargetScore必须关联一个Student")

        # 自动设置显示名称
        self.set_display_name()

        # 自动计算地域影响
        self.calculate_region_adjustment()

        # 自动更新成绩趋势
        self.update_score_trend()

        self.clean()
        super().save(*args, **kwargs)

    def set_display_name(self):
        """根据学生科目配置自动设置显示名称"""
        if self.subject_category == 'politics':
            self.display_name = '政治'
        elif self.subject_category == 'english':
            self.display_name = self.student.english_type
        elif self.subject_category == 'math':
            self.display_name = self.student.math_type
        elif self.subject_category == 'professional_1':
            self.display_name = self.student.professional_subject_name or '专业课一'
        elif self.subject_category == 'professional_2':
            self.display_name = self.student.professional_subject_2_name or '专业课二'
        else:
            self.display_name = self.get_subject_category_display()

    def calculate_region_adjustment(self):
        """自动计算地域影响分数调整 - 使用学生的考试地区"""
        adjustment = 0

        # 使用学生的考试地区
        region = self.student.target_exam_region

        # 政治科目地域影响最大
        if self.subject_category == 'politics':
            region_impact = {
                '北京': -8,  # 北京政治压分最严重
                '上海': -6,  # 上海也比较严格
                '江苏': -4,  # 江苏严格
                '浙江': -4,  # 浙江严格
                '湖北': -2,  # 湖北中等
                '广东': 0,  # 广东相对宽松
                '四川': 0,  # 四川相对宽松
                '其他': 0,  # 其他地区标准
            }
            adjustment = region_impact.get(region, 0)

        # 英语科目也有轻微地域影响
        elif self.subject_category == 'english':
            if region in ['北京', '上海', '江苏', '浙江']:
                adjustment = -2

        self.region_adjustment = adjustment

    def update_score_trend(self):
        """更新成绩趋势（简化版，实际应该基于历史模拟成绩）"""
        if self.last_mock_score:
            if self.last_mock_score > self.current_level + 5:
                self.mock_score_trend = '上升'
            elif self.last_mock_score < self.current_level - 5:
                self.mock_score_trend = '下降'
            else:
                self.mock_score_trend = '稳定'

    @property
    def adjusted_targets(self):
        """计算考虑地域影响后的各档目标分数"""
        return {
            'ideal': max(0, self.ideal_target + self.region_adjustment),
            'realistic': max(0, self.realistic_target + self.region_adjustment),
            'safety': max(0, self.safety_target + self.region_adjustment),
        }

    @property
    def max_score(self):
        """该科目满分 - 处理 student 为 None 的情况"""
        if not self.student:
            # 如果没有 student，返回默认值
            max_scores = {
                'politics': 100,
                'english': 100,
                'math': 150,
                'professional_1': 150,
                'professional_2': 150,
            }
            return max_scores.get(self.subject_category, 150)

        # 原有逻辑
        max_scores = {
            'politics': 100,
            'english': 100,
            'math': 150,
            'professional_1': 150,
            'professional_2': 150,
        }
        return max_scores.get(self.subject_category, 150)

    @property
    def progress_to_realistic(self):
        """到实际目标的进度百分比"""
        if (self.realistic_target is not None and
                self.current_level is not None and
                self.realistic_target > self.current_level):
            total_gap = self.realistic_target - self.current_level
            covered = self.current_level - (self.safety_target or 0)
            if total_gap > 0:
                return min(100, (covered / total_gap) * 100)
        return 100 if (self.current_level is not None and
                       self.realistic_target is not None and
                       self.current_level >= self.realistic_target) else 0

    @property
    def risk_level(self):
        """风险评估"""
        # 基于分数差距、时间压力、基础难度等因素
        risk_score = 0

        # 分数差距风险 - 添加 None 值检查
        if (self.realistic_target is not None and
                self.current_level is not None):
            gap_ratio = (self.realistic_target - self.current_level) / self.max_score
            if gap_ratio > 0.4:
                risk_score += 3
            elif gap_ratio > 0.25:
                risk_score += 2
            elif gap_ratio > 0.15:
                risk_score += 1

        # 基础难度风险
        if self.base_difficulty >= 4:
            risk_score += 2
        elif self.base_difficulty >= 3:
            risk_score += 1

        # 目标难度风险
        if self.goal_difficulty >= 4:
            risk_score += 2
        elif self.goal_difficulty >= 3:
            risk_score += 1

        # 信心风险
        if self.confidence_level < 30:
            risk_score += 2
        elif self.confidence_level < 50:
            risk_score += 1

        if risk_score >= 6:
            return 'high'
        elif risk_score >= 4:
            return 'medium'
        else:
            return 'low'

    @property
    def days_remaining(self):
        """剩余备考天数"""
        if self.target_completion_date:
            delta = self.target_completion_date - datetime.timezone.now().date()
            return max(0, delta.days)
        return None

    @property
    def study_days(self):
        """已学习天数"""
        if self.study_start_date:
            delta = timezone.now().date() - self.study_start_date
            return max(0, delta.days)
        return 0

    def clean(self):
        """数据验证"""
        errors = {}

        # 检查 student 是否存在
        if not self.student_id and not hasattr(self, 'student'):
            # 如果 student 不存在，跳过需要 student 的验证
            return

        max_score = self.max_score

        # 验证分数范围 - 添加 None 值检查
        if self.ideal_target is not None and self.ideal_target > max_score:
            errors['ideal_target'] = f'{self.display_name}理想目标不能超过{max_score}分'

        if self.realistic_target is not None and self.realistic_target > max_score:
            errors['realistic_target'] = f'{self.display_name}实际目标不能超过{max_score}分'

        if self.safety_target is not None and self.safety_target > max_score:
            errors['safety_target'] = f'{self.display_name}保底分数不能超过{max_score}分'

        if self.current_level is not None and self.current_level > max_score:
            errors['current_level'] = f'{self.display_name}当前水平不能超过{max_score}分'

        # 验证分数逻辑 - 添加 None 值检查
        if (self.ideal_target is not None and
                self.realistic_target is not None and
                self.ideal_target < self.realistic_target):
            errors['ideal_target'] = '理想目标应不低于实际目标'

        if (self.realistic_target is not None and
                self.safety_target is not None and
                self.realistic_target < self.safety_target):
            errors['realistic_target'] = '实际目标应不低于保底分数'

        # 验证科目有效性 - 只有在有 student 时才验证
        if self.student and not self.is_subject_valid():
            errors['subject_category'] = '该科目与学生考试科目配置不匹配'

        if errors:
            raise ValidationError(errors)

    def is_subject_valid(self):
        """验证科目是否与学生配置匹配 - 处理 student 为 None 的情况"""
        if not self.student:
            return True  # 如果没有 student，暂时返回 True

        if self.subject_category == 'politics':
            return self.student.has_politics
        elif self.subject_category == 'english':
            return self.student.has_english
        elif self.subject_category == 'math':
            return self.student.has_math
        elif self.subject_category == 'professional_1':
            return bool(self.student.professional_subject_name)
        elif self.subject_category == 'professional_2':
            return self.student.has_professional_2 and bool(self.student.professional_subject_2_name)
        return False